# coding:utf8
import sys
import io
import json
import pickle
import numpy as np
import math
from scipy.optimize import curve_fit
import random


class ContextHost(object):
    class Context(object):
        def __init__(self, data):
            self.data = data

        def maxbuy(self):
            return self.host.blance / self.data[-1]["Close"] * self.host.leverage

        def buy(self, amont, stoploss=float("NaN"), stopearn=float("NaN")):
            if amont <= self.maxbuy():
                self.host.litera.append({
                    "srcdata": self.data[-1],
                    "time": self.data[-1]["Date"],
                    "type": 1,
                    "prise": self.data[-1]["Close"],
                    "amont": amont,
                    "range": [stoploss, stopearn]
                })
                # mkprice = self.data[-1]["Close"] * amont
                # self.host.blance -= mkprice / self.host.leverage

        def buyNow(self, amont, stoploss=float("NaN"), stopearn=float("NaN")):
            self.buy(amont, self.data[-1]["Close"] + stoploss, self.data[-1]["Close"] + stopearn)

        def sell(self, amont, stoploss=float("NaN"), stopearn=float("NaN")):
            if amont <= self.maxbuy():
                self.host.litera.append({
                    "srcdata": self.data[-1],
                    "time": self.data[-1]["Date"],
                    "type": -1,
                    "prise": self.data[-1]["Close"],
                    "amont": amont,
                    "range": [stoploss, stopearn]
                })
                # mkprice = self.data[-1]["Close"] * amont
                # self.host.blance -= mkprice / self.host.leverage

        def sellNow(self, amont, stoploss=float("NaN"), stopearn=float("NaN")):
            self.sell(amont, self.data[-1]["Close"] + stoploss, self.data[-1]["Close"] + stopearn)

        def close(self, j):
            if j in self.host.litera:
                dline = self.data[-1]
                if j['srcdata'] == dline:
                    print("错误，开仓和平仓在同一时间，请检查")
                    # raise Exception("错误，开仓和平仓在同一时间，请检查")
                j["close_data"] = dline
                j["close_prise"] = dline["Close"]
                j["close_time"] = dline["Date"]
                if j["type"] == 1:
                    self.host.blance += (j["close_prise"] - j["prise"]) * j["amont"] - self.host.spread * j["amont"]
                    j["success"] = j["close_prise"] > j["prise"]
                elif j["type"] == -1:
                    self.host.blance += (j["prise"] - j["close_prise"]) * j["amont"] - self.host.spread * j["amont"]
                    j["success"] = j["close_prise"] < j["prise"]
                self.host.closelitera.append(j)
                self.host.litera.remove(j)

        def closebuy(self):
            for j in self.host.litera:
                if j["type"] == 1:
                    self.close(j)

        def closesell(self):
            for j in self.host.litera:
                if j["type"] == -1:
                    self.close(j)

        def litera(self):
            return self.host.litera[:]

        def closelitera(self):
            return self.host.closelitera[:]

        def blance(self):
            return self.host.blance

        def marketvalue(self):
            return self.host.marketvalue

        def baseblance(self):
            return self.host.baseblance

        def leverage(self):
            return self.host.leverage

        def spread(self):
            return self.host.spread

        def point(self):
            return self.host.point

        def product(self):
            return self.host.product

        def calcgap(self):
            return self.host.calcgap

        def qt_type(self):
            return self.host.qt_type

    def __init__(self, data, blance=10000000):
        self.spread = 0.02  # 点差
        self.point = 0.001  # 点差
        self.leverage = 10  # 杠杆
        self.calcgap = 250
        self.qt_type = 15
        self.data = data
        self.product = 'FXUSDJPY'
        self.litera = []
        self.closelitera = []
        self.baseblance = blance
        self.blance = blance
        self.marketvalue = blance
        self.hodingsum = 0  # 手*bar数
        self.hodingpricesum = 0  # 价格*bar数

    def doLoop(self, fun):
        for i in range(self.calcgap, len(self.data)):
            self.nowindex = i
            dline = self.data[i]

            for j in self.litera[:]:
                if j["type"] == 1:
                    if j["range"][0] >= dline["Low"]:
                        j["close_data"] = dline
                        j["close_prise"] = j["range"][0]
                        j["close_time"] = dline["Date"]
                        j["success"] = False
                        self.blance += (j["close_prise"] - j["prise"]) * j["amont"] - self.spread * j["amont"]
                        self.closelitera.append(j)
                        self.litera.remove(j)
                    elif j["range"][1] < dline["High"]:
                        j["close_data"] = dline
                        j["close_prise"] = j["range"][1]
                        j["close_time"] = dline["Date"]
                        j["success"] = True
                        self.blance += (j["close_prise"] - j["prise"]) * j["amont"] - self.spread * j["amont"]
                        self.closelitera.append(j)
                        self.litera.remove(j)
                elif j["type"] == -1:
                    if j["range"][0] <= dline["High"]:
                        j["close_data"] = dline
                        j["close_prise"] = j["range"][0]
                        j["close_time"] = dline["Date"]
                        j["success"] = False
                        self.blance += (j["prise"] - j["close_prise"]) * j["amont"] - self.spread * j["amont"]
                        self.closelitera.append(j)
                        self.litera.remove(j)
                    elif j["range"][1] > dline["Low"]:
                        j["close_data"] = dline
                        j["close_prise"] = j["range"][1]
                        j["close_time"] = dline["Date"]
                        j["success"] = True
                        self.blance += (j["prise"] - j["close_prise"]) * j["amont"] - self.spread * j["amont"]
                        self.closelitera.append(j)
                        self.litera.remove(j)

            ctx = ContextHost.Context(self.data[i - self.calcgap:i])
            ctx.host = self
            fun(ctx)

            now_mp = 0
            for j in self.litera:
                if j["type"] == 1:
                    now_mp += (dline['Close'] - j["prise"]) * j["amont"] - self.spread * j["amont"]
                elif j["type"] == -1:
                    now_mp += (j["prise"] - dline['Close']) * j["amont"] - self.spread * j["amont"]
            self.marketvalue = now_mp + self.blance
            dline["mv"] = self.marketvalue
            dline["blance"] = self.blance
            for j in self.litera:
                self.hodingsum += j["amont"]
                self.hodingpricesum += j["prise"] * j["amont"]

    @classmethod
    def profitfun(cls, x, a, b, c):
        return a * np.power((1 + b), x + c)

    def ProfitRate(self):
        pf = self.marketvalue - self.baseblance
        xl = pf / len(self.data)
        year = xl * (250 * 22 * 60 / self.qt_type) * 100 / self.baseblance
        # return xl, year

        mvs = []
        for one in self.data:
            if "mv" in one:
                mvs.append(one["mv"])
        xdata = np.array(range(len(mvs)), dtype=np.float64)
        ydata = np.array(mvs)
        popt, pcov = curve_fit(type(self).profitfun, xdata, ydata,
                               p0=np.array([self.baseblance, 0, 0], dtype=np.float64))
        return xl, year, popt, np.sqrt(np.diag(pcov))

    def MaxRollBack(self):

        # mv = [[o['mv'], o['Date']] if "mv" in o else [10000, 0] for o in self.data]
        # mv = sorted(mv, key=lambda a: a[0], reverse=False)
        # return (self.baseblance - mv[0][0]) * 100 / self.baseblance, mv[0][1]

        # ml = 0
        # tm = 0
        # for i in range(len(self.data)):
        #     if 'blance' not in self.data[i]:
        #         continue
        #     bl = [o['blance'] if "blance" in o else 10000 for o in self.data[:i]]
        #     if ml < (max(bl) - self.data[i]['blance']) / max(bl):
        #         ml = (max(bl) - self.data[i]['blance']) / max(bl)
        #         tm = self.data[i]['Date']
        # ml1 = 0
        # tm1 = 0
        # bl = [o['mv'] if "mv" in o else self.baseblance for o in self.data]
        # for i in range(2, len(self.data) - 2):
        #     mx = max(bl[:i])
        #     mn = min(bl[i:])
        #     nml = (mx - mn) / mx
        #     if ml1 < nml:
        #         ml1 = nml
        #         tm1 = self.data[i]['Date']
        # return ml1 * 100, tm1
        #
        # ml1 = 0
        # tm1 = 0
        # for i in range(len(self.data)):
        #     if 'mv' not in self.data[i]:
        #         continue
        #     bl = [o['mv'] if "mv" in o else 10000 for o in self.data[:i]]
        #     mx = max(bl)
        #     if ml1 < (mx - self.data[i]['mv']) / mx:
        #         ml1 = (mx - self.data[i]['mv']) / mx
        #         tm1 = self.data[i]['Date']
        # return ml1 * 100, tm1

        lastmaxmv = 0
        maxrollback = 0
        maxrollbackdate = None
        for one in self.data:
            if "mv" in one:
                if one["mv"] > lastmaxmv:
                    lastmaxmv = one["mv"]
                else:
                    rollback = (lastmaxmv - one["mv"]) / lastmaxmv
                    if maxrollback < rollback:
                        maxrollback = rollback
                        maxrollbackdate = one["Date"]
        return maxrollback * 100, maxrollbackdate

    def SuccessRate(self):
        sc = 0
        ball = bs = sall = ss = 0
        realclose = [o for o in self.closelitera if o["amont"] > 0]
        for one in realclose:
            if one["success"]:
                sc += 1
            if one["type"] == 1:
                ball += 1
                if one["success"]:
                    bs += 1
            if one["type"] == -1:
                sall += 1
                if one["success"]:
                    ss += 1
        return 0 if len(realclose) == 0 else float(sc) / len(realclose) * 100, 0 if ball == 0 else float(
            bs) / ball * 100, 0 if sall == 0 else float(ss) / sall * 100, len(realclose), ball, sall


def run(context):
    opsize = 20.0
    # opsize=context.maxbuy()
    if random.random() < 0.5:
        context.buy(opsize, context.data[-1]["Close"] - 10, context.data[-1]["Close"] + 60)
    if random.random() < 0.5:
        context.sell(opsize, context.data[-1]["Close"] + 5, context.data[-1]["Close"] - 40)


def getv(one, key):
    if key in one:
        return one[key]
    else:
        return float("NaN")


def DrawIndex(ax, datalist):
    poslist = [one["pos"] for one in datalist]
    disres = [getv(one, "wsvc") for one in datalist]
    ax.plot(poslist, [getv(one, "svc") for one in datalist], color="g")
    ax.plot(poslist, disres, color="r")
    ax.hlines(0, poslist[0], poslist[-1], colors='b')
    ax.hlines([10, 5, 0, -5, -10], poslist[0], poslist[-1], colors=['r', 'r', 'b', 'g', 'g'], linestyles='dashdot')


def goShow(ch, DrawIndex):
    import show
    xl, year, popt, perr = ch.ProfitRate()
    maxrb = ch.MaxRollBack()[0]
    # print("利率每bar:%f%%,利率方差:%f%%,年化利率:%f%%" % (
    #     popt[1] * 100, perr[1] / popt[1] * 100, ((popt[1] + 1) ** (250 * 22 * 4) - 1) * 100))
    # print("利率每bar:%f%%,利率方差:%f%%,年化利率:%f%%" % (xl, pow(abs(xl), 0.5), year))
    print("年化利率:%f%%" % year)
    # print(len(ch.data))
    print("成功率:%f%%,多单成功率:%f%%,空单成功率:%f%%,总单数:%d,多单数:%d,空单数:%d" % ch.SuccessRate())
    print("净值最大回撤:%f%% , 利润回测比：%f" % (maxrb, year / maxrb))
    print("最终市值:%f" % ch.marketvalue)
    print("初始市值:%f" % ch.baseblance)
    print("杠杆:%d倍" % ch.leverage)

    # type(ch).profitfun()
    def DrawMarketValue(ax, datalist):
        poslist = [one["pos"] for one in datalist]
        disres = [getv(one, "mv") for one in datalist]

        nancount = 0
        for one in disres:
            if math.isnan(one):
                nancount += 1
            else:
                break
        funline = [float("nan")] * nancount + type(ch).profitfun(
            np.array(range(len(poslist) - nancount), dtype=np.float64), *popt).tolist()

        blist = [getv(one, "blance") for one in datalist]
        ax.plot(poslist, funline, color="b")
        ax.plot(poslist, blist, color="g")
        ax.plot(poslist, disres, color="r", linewidth=2.0)

    def DrawOpPoint(ax, ch, datalist):
        for cl in ch.closelitera + ch.litera:
            pos = cl["srcdata"]["pos"]
            if cl["type"] == 1:
                y1 = cl["prise"] - 0.015
                if "close_data" in cl:
                    y2 = cl["close_prise"] - 0.015
                    pos2 = cl["close_data"]["pos"]
                    ax.plot((pos, pos2), (y1, y2), "--o", color=(1.0, 0, 0), alpha=0.5)
                    # ax.arrow(pos, y1, pos2 - pos, y2 - y1, length_includes_head=True, shape="left", head_width=0.03,
                    #          alpha=0.5, ls="--", color=(1.0, 0, 0))
                else:
                    ax.plot(pos, y1, "ro")
            elif cl["type"] == -1:
                y1 = cl["prise"] + 0.015
                if "close_data" in cl:
                    y2 = cl["close_prise"] + 0.015
                    pos2 = cl["close_data"]["pos"]
                    ax.plot((pos, pos2), (y1, y2), "--o", color=(0, 1.0, 0), alpha=0.5)
                    # ax.arrow(pos, y1, pos2 - pos, y2 - y1, length_includes_head=True, shape="right", head_width=0.03,
                    #          alpha=0.5, ls="--", color=(0, 1, 0))
                else:
                    ax.plot(pos, y1, "go")

    def exDrawLine(axs, datalist):
        DrawOpPoint(axs[0], ch, datalist)
        DrawIndex(axs[1], datalist)
        DrawMarketValue(axs[2], datalist)

    show.Show(ch.data, exDrawLine, ch.product, ch.qt_type)


if __name__ == "__main__":
    datalist = fetchdata.loaddate("data/USDJPYdata.gz")
    ch = ContextHost(datalist[-300:])
    ch.doLoop(run)
    # print ch.hodingsum,ch.hodingpricesum
    goShow(ch, DrawIndex)
